﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.RegularExpressions;
using BitMiracle.LibTiff.Classic;

namespace TIFPDFCounter
{
	public class TPCFile
	{
		#region Constructor
		public TPCFile()
		{
		}
		
		public TPCFile(string fileName)
		{
			Name = fileName;
			
			if (Extension == ".PDF")
			{
				PageCount = GetPDFPageCount();
				Pages = GetPDFPageSizes();
			}
			else if (Extension == ".TIF")
			{
				PageCount = GetTIFPageCount();
				Pages = GetTIFPageSizes();
			}
			else
			{
				throw new Exception("Unsupported file format.");
			}
		}
		#endregion
		
		#region Public Accessors
		public string Name { get; set; }
		public int PageCount { get; set; }
		public List<Size> Pages { get; set; }
		public string Extension
		{
			get
			{
				try
				{
					return Path.GetExtension(Name).ToUpper();
				}
				catch (Exception)
				{
					return "";
				}
				
			}
		}
		#endregion
		
		#region TIF Methods
		
		int GetTIFPageCount()
		{
			using (Tiff tif = Tiff.Open(@Name, "r"))
			{
				return tif.NumberOfDirectories();
			}
		}
		
		List<Size> GetTIFPageSizes()
		{
			var sizes = new List<Size>();
			using (Tiff tif = Tiff.Open(@Name, "r"))
			{
				for (short i = 0; i < tif.NumberOfDirectories(); i++)
				{
					tif.SetDirectory(i);
					FieldValue[] field;
					field = tif.GetField(TiffTag.IMAGEWIDTH);
					int pxWidth = field != null ? field[0].ToInt() : 0;
					field = tif.GetField(TiffTag.IMAGELENGTH);
					int pxHeight = field != null ? field[0].ToInt() : 0;
					field = tif.GetField(TiffTag.XRESOLUTION);
					decimal xRes = field != null ? (decimal)field[0].ToFloat() : 0.0m;
					field = tif.GetField(TiffTag.YRESOLUTION);
					decimal yRes = field != null ? (decimal)field[0].ToFloat() : 0.0m;
					field = tif.GetField(TiffTag.RESOLUTIONUNIT);
					ResUnit resUnit = field != null ? (ResUnit)field[0].ToInt() : ResUnit.INCH;
					if (resUnit == ResUnit.CENTIMETER)
					{
						xRes /= 2.54m;
						yRes /= 2.54m;
						resUnit = ResUnit.INCH;
					}
					
					var size = new Size();
					size.Width.Nominal = pxWidth / xRes;
					size.Height.Nominal = pxHeight / yRes;
					sizes.Add(size);
				}
			}
			return sizes;
		}
		#endregion
		
		#region PDF Methods
		const string pdfExe = @"cpdf.exe";
		
		int GetPDFPageCount()
		{
			Process proc = new Process();
			proc.StartInfo.FileName = pdfExe;
			proc.StartInfo.Arguments = string.Concat(@"-pages """, @Name, @"""");
			proc.StartInfo.CreateNoWindow = true;
			proc.StartInfo.RedirectStandardOutput = true;
			proc.StartInfo.RedirectStandardError = true;
			proc.StartInfo.UseShellExecute = false;
			proc.Start();
			
			string line;
			int pageCount = 0;
			
			while ((line = proc.StandardOutput.ReadLine()) != null)
			{
				if (int.TryParse(line, out pageCount))
					break;
			}
			proc.WaitForExit();
			if (pageCount < 1)
				throw new Exception("Could not get the PDF page count.");
			
			Debug.WriteLine("PageCount = " + pageCount);
			return pageCount;
		}
		
//		Example output of cpdf.exe
//			
//		Page 1:
//		MediaBox: 0.000000 0.000000 2592.000000 1745.280000
//		CropBox:
//		BleedBox:
//		TrimBox:
//		ArtBox:
//		Rotation: 90

//	  	Group1 = Page #
//		Group2 = mediabox top-left x
//		Group3 = mediabox top-left y
//		Group4 = mediabox width
//		Group5 = mediabox height
//		Group6 = cropbox top-left x
//		Group7 = cropbox top-left y
//		Group8 = cropbox width
//		Group9 = cropbox height
		
		List<Size> GetPDFPageSizes()
		{
			List<Size> pages = new List<Size>();
			Process proc = new Process();
			proc.StartInfo.FileName = pdfExe;
			proc.StartInfo.Arguments = string.Concat(@"-page-info """, @Name, @"""");
			proc.StartInfo.CreateNoWindow = true;
			proc.StartInfo.RedirectStandardOutput = true;
			proc.StartInfo.RedirectStandardError = true;
			proc.StartInfo.UseShellExecute = false;
			proc.Start();
			
			Regex rxPageDims = new Regex(@"Page\s(\d+):\r?\nMediaBox:\s([-]?[0-9]+\.[0-9]+)\s([-]?[0-9]+\.[0-9]+)\s([-]?[0-9]+\.[0-9]+)\s([-]?[0-9]+\.[0-9]+)\r?\nCropBox:\s(?:([-]?[0-9]+\.[0-9]+)\s([-]?[0-9]+\.[0-9]+)\s([-]?[0-9]+\.[0-9]+)\s([-]?[0-9]+\.[0-9]+))?", RegexOptions.ECMAScript | RegexOptions.Multiline);
			
			string output = proc.StandardOutput.ReadToEnd();
			Debug.WriteLine("*** Output ***");
			Debug.Write(output);
			Debug.WriteLine("*** END ***");
			
			MatchCollection mc = rxPageDims.Matches(output);
			if (mc.Count != this.PageCount)
				throw new Exception("PageCount=" + PageCount + ", Matches.Count=" + mc.Count);
			for (int i = 0; i < mc.Count; i++)
			{
				int pageNum = 0;
				if (!int.TryParse(mc[i].Groups[1].Value, out pageNum))
					throw new Exception("Could not parse page number " + (i + 1) + ".");
				if (pageNum != (i + 1))
					throw new Exception("Page " + (i + 1) + " number mismatch.");
				
				decimal topleftx, toplefty, width, height = 0;
					
				// try crop box first
				if (decimal.TryParse(mc[i].Groups[6].Value, out topleftx) &&
					decimal.TryParse(mc[i].Groups[7].Value, out toplefty) &&
					decimal.TryParse(mc[i].Groups[8].Value, out width) &&
					decimal.TryParse(mc[i].Groups[9].Value, out height))
				{
					//
				}
				else
				{
					// try media box
					decimal.TryParse(mc[i].Groups[2].Value, out topleftx);
					decimal.TryParse(mc[i].Groups[3].Value, out toplefty);
					decimal.TryParse(mc[i].Groups[4].Value, out width);
					decimal.TryParse(mc[i].Groups[5].Value, out height);
				}
				
				width -= topleftx;
				height -= toplefty;
				Debug.WriteLine("Page=" + pageNum + ", Width=" + width + ", Height=" + height);
				
				if (!(width > 0.0m && height > 0.0m))
					throw new Exception("Page " + pageNum + ": Width or height less than zero.");
				Size size = new Size();
				size.Width.Nominal = width / 72.0m;
				size.Height.Nominal = height / 72.0m;
				pages.Add(size);
			}
			
			proc.WaitForExit();
			return pages;
		}
		#endregion
	}
}
